package com.ldf.jrtsp.parser;

import com.ldf.jrtsp.callback.BareStreamCallBack;
import com.ldf.jrtsp.enums.RtpPayloadType;
import com.ldf.jrtsp.exception.JRtspException;
import com.sun.jna.Memory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;

import static com.ldf.jrtsp.constants.RtspConstants.RTSP_RTP_DATA_WAIT_TIME;

/**
 * rtp load H265数据解析
 *
 * @author lidaofu
 * @since 2024/6/21
 **/
public class RtpLoadH265DataParser implements IRtpLoadCodecParser {
    private static final Logger log = LoggerFactory.getLogger(RtpLoadH265DataParser.class);
    private final LinkedBlockingQueue<RtpDataParser> rtpDataQueue;
    private final BareStreamCallBack bareStreamCallBack;
    //为了和ZLM4J交互这里使用jna的memory 可以替换为nio的buffer 或者直接byte[]
    private Memory buffer;
    private int fuOffset = 0;
    private int naluDataSize = 0;


    public RtpLoadH265DataParser(BareStreamCallBack bareStreamCallBack) {
        this.rtpDataQueue = new LinkedBlockingQueue<>(256);
        this.bareStreamCallBack = bareStreamCallBack;
        byte[] startCode = new byte[]{0, 0, 0, 1};
        this.buffer = new Memory(1024 * 1024);
        this.buffer.write(0, startCode, 0, startCode.length);
    }

    /**
     * 发送rtp数据
     *
     * @param rtpDataParser
     */
    @Override
    public void sendRtpPacket(RtpDataParser rtpDataParser) {
        try {
            this.rtpDataQueue.offer(rtpDataParser, RTSP_RTP_DATA_WAIT_TIME, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            log.error("send rtp packet error queue full");
            throw new JRtspException("send rtp packet error queue full", e);
        }
    }

    @Override
    public void run() {
        while (true) {
            try {
                RtpDataParser rtpDataParser = this.rtpDataQueue.poll(RTSP_RTP_DATA_WAIT_TIME, TimeUnit.SECONDS);
                if (rtpDataParser == null) {
                    buffer.close();
                    break;
                }
                byte[] rawData = rtpDataParser.getRawData();
                int fuNaluType = (rawData[0] >> 1) & 0xFF;
                switch (fuNaluType) {
                    case 1:
                    case 16:
                    case 19:
                    case 32:
                    case 33:
                    case 34:
                    case 39:
                        buffer.write(4, rawData, 0, rawData.length);
                        naluDataSize = rawData.length + 4;
                        bareStreamCallBack.callBack(RtpPayloadType.H265, buffer.share(0), naluDataSize, rtpDataParser.getTimestamp());
                        naluDataSize = 0;
                        break;
                    case 49: //FU-A
                        int fuCode = (rawData[2] & 0xFF) >> 6;
                        switch (fuCode) {
                            case 0:
                                buffer.write(fuOffset, rawData, 3, rawData.length - 3);
                                fuOffset = rawData.length + fuOffset - 3;
                                break;
                            case 1:
                                buffer.write(fuOffset, rawData, 3, rawData.length - 3);
                                fuOffset = rawData.length + fuOffset - 3;
                                bareStreamCallBack.callBack(RtpPayloadType.H265, buffer.share(0), fuOffset, rtpDataParser.getTimestamp());
                                fuOffset = 0;
                                break;
                            case 2:
                                byte naluType = (byte) ((((rawData[2]&0xFF)&0x3F)<<1)|0x81);
                                rawData[2] = rawData[1];
                                rawData[1] = (byte) (((rawData[0]&0xFF)|0x7E) & (naluType&0xFF));
                                buffer.write(4, rawData, 1, rawData.length - 1);
                                fuOffset = rawData.length + 3;
                                byte[] h265Data=new byte[6];
                                buffer.read(0, h265Data, 0, 6);
                                break;
                        }
                        break;

                }
            } catch (InterruptedException e) {
                log.error("take rtp packet error");
                throw new JRtspException("take rtp packet error", e);
            }
        }
    }
}